﻿

IncludePath "Source/"
IncludeFile "threeD.pb"
IncludePath "/"

EnableExplicit

;{ Constants
	#DEPTH_TEXTURE_WIDTH = 1024
	#DEPTH_TEXTURE_HEIGHT = 1024
;}
;{ Globals
	Global ShaderID.l
	Global ShaderShadowID.l
	Global TextureDiffuseID.l
	Global TextureNormalsID.l
	Global TextureDepth.l
	Global DepthFBO.l
	Global Mesh.l
	Global hDC.l
;}

;{ Model
	Global Model_Position.vec3
	Global Model_Rotation.vec4
	Global Model_Matrix.mat4
	Procedure Model_UpdateParams()
		Protected t.f, angle.f, vAxe.vec3
		t = timeGetTime_()/1000
		Vec3_Set(vAxe, 1, 0, 0) : Vec3_Normalize(vAxe) : angle = #PI/3;t/10
		Quat_FromAxis(Model_Rotation, vAxe, angle)
		Vec3_Set(Model_Position, Pow(Sin(t), 5)/5, 0, 0)
	EndProcedure
	Procedure Model_UpdateMatrix()
		Protected.vec4 pos
		Protected.mat3 mRot
		Mat3_FromQuat(mRot, Model_Rotation)
		Vec4_Vec(pos, Model_Position, 1)
		Mat4_SetM(Model_Matrix, mRot, pos)
	EndProcedure
	Procedure Model_Update()
		Model_UpdateParams()
		Model_UpdateMatrix()
	EndProcedure
;}
;{ Camera
	Global Camera_Position.vec3
	Global Camera_Rotation.vec4
	Global Camera_Matrix.mat4
	Global Camera_ViewMatrix.mat4
	Global Camera_ProjectionMatrix.mat4
	Procedure Camera_UpdateParams()
		Protected t.f, angle.f, vAxe.vec3
		t = timeGetTime_()/1000
		Vec3_Set(vAxe, 0, 1, 0) : Vec3_Normalize(vAxe) : angle = 2*(WindowMouseY(0)/WindowHeight(0) - 0.5)
		Quat_FromAxis(Camera_Rotation, vAxe, angle)
		Vec3_Set(Camera_Position, 0, 0, 3)
	EndProcedure
	Procedure Camera_UpdateMatrix()
		Protected.mat4 translation, rotation
		Protected.vec3 pos
		Protected.vec4 rot
		Vec4_Copy(rot, Camera_Rotation)
		Vec3_Copy(pos, Camera_Position)
		Mat4_FromQuat(rotation, rot)
		Mat4_CreateTranslation(translation, pos)
		Mat4_MulM(Camera_Matrix, translation, rotation)
		Mat4_Inverse(Camera_ViewMatrix, Camera_Matrix)
		Mat4_CreatePerspective(Camera_ProjectionMatrix, #PI/2, WindowWidth(0)/WindowHeight(0), 1/10, 10)
	EndProcedure
	Procedure Camera_Update()
		Camera_UpdateParams()
		Camera_UpdateMatrix()
	EndProcedure
;}
;{ Light
	Global Light_Direction.vec3
	Global Light_Matrix.mat4
	Global Light_ViewMatrix.mat4
	Global Light_OffsetMatrix.mat4
	Global Light_ProjectionMatrix.mat4
	Procedure Light_UpdateParams()
		Protected.vec3 offset
		Vec3_Set(offset, -1, -1, 0)
		Vec3_Set(Light_Direction, WindowMouseX(0)/WindowWidth(0), 1-WindowMouseY(0)/WindowHeight(0), -0.01)
		Vec3_Scale(Light_Direction, Light_Direction, 2)
		Vec3_Add(Light_Direction, Light_Direction, offset)
		Vec3_Normalize(Light_Direction)
		Vec3_Set(Light_Direction, -1, 0, -0.01)
		Vec3_Normalize(Light_Direction)
	EndProcedure
	Procedure Light_UpdateMatrix()
		Protected.vec4 offset, rot
		Protected.vec3 up, dir
		Protected.mat3 mRot
		Vec3_Random(up)
		Mat4_CreateLookAt(Light_Matrix, Light_Direction, Vec3_Null, up)
		Mat4_Inverse(Light_ViewMatrix, Light_Matrix)
		Mat4_CreateOrtho(Light_ProjectionMatrix, -2, 2, -2, 2, -3, 3)
		Vec4_Vec(offset, Light_Direction, 0)
		Vec4_Scale(offset, offset, 0.1)
		offset\w = 1
		Mat4_CreateTranslation(Light_OffsetMatrix, offset)
	EndProcedure
	Procedure Light_Update()
		Light_UpdateParams()
		Light_UpdateMatrix()
	EndProcedure
;}
;{ Procedures
	Procedure.l TextureCreateDepth(width.l, height.l)
		Protected.l texture
		; запросим у OpenGL свободный индекс текстуры
		glGenTextures(1, @texture)
		
		; сделаем текстуру активной
		glBindTexture(#GL_TEXTURE_2D, texture)
		
		; установим параметры фильтрации текстуры - линейная фильтрация
		glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_MIN_FILTER, #GL_NEAREST)
		glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_MAG_FILTER, #GL_NEAREST)
		
		; установим параметры "оборачиваниея" текстуры - отсутствие оборачивания
		glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_WRAP_S, #GL_CLAMP_TO_EDGE)
		glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_WRAP_T, #GL_CLAMP_TO_EDGE)
		
		; необходимо для использования depth-текстуры как shadow Map
		glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_COMPARE_MODE, #GL_COMPARE_REF_TO_TEXTURE)
		
		; соаздем "пустую" текстуру под depth-данные
		glTexImage2D_(#GL_TEXTURE_2D, 0, #GL_DEPTH_COMPONENT, width, height, 0, #GL_DEPTH_COMPONENT, #GL_FLOAT, #Null)
		
		; проверим на наличие ошибок
		;OPENGL_CHECK_FOR_ERRORS()
		
		ProcedureReturn texture
	EndProcedure
	Procedure InitTextures()
		;glActiveTexture(#GL_TEXTURE0)
		;TextureDiffuseID = OpenGL_ImageToTexture(LoadImage(#PB_Any, "Data/stonetile.png"))
		;glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_MIN_FILTER, #GL_LINEAR)
		;glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_MAG_FILTER, #GL_LINEAR)
		
		;glActiveTexture(#GL_TEXTURE1)
		;TextureNormalsID = OpenGL_ImageToTexture(LoadImage(#PB_Any, "Data/normalmap.png"))
		;glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_MIN_FILTER, #GL_LINEAR)
		;glTexParameteri_(#GL_TEXTURE_2D, #GL_TEXTURE_MAG_FILTER, #GL_LINEAR)
		
		; создадим текстуру для хранения буфера глубины
		;glActiveTexture(#GL_TEXTURE0)
		TextureDepth = TextureCreateDepth(#DEPTH_TEXTURE_WIDTH, #DEPTH_TEXTURE_HEIGHT)
	EndProcedure
	Procedure CreateFBO()
		; Framebuffer Object (FBO) для рендера в него буфера глубины
		DepthFBO = 0
		; переменная для получения состояния FBO
		Protected.l fboStatus
		
		; создаем FBO для рендера глубины в текстуру
		glGenFramebuffers(1, @DepthFBO)
		; делаем созданный FBO текущим
		glBindFramebuffer(#GL_FRAMEBUFFER, DepthFBO)
		
		; отключаем вывод цвета в текущий FBO
		glDrawBuffer_(#GL_NONE)
		glReadBuffer_(#GL_NONE)
		
		; указываем для текущего FBO текстуру, куда следует производить рендер глубины
		glFramebufferTexture(#GL_FRAMEBUFFER, #GL_DEPTH_ATTACHMENT, TextureDepth, 0)
		
		; проверим текущий FBO на корректность
		If (fboStatus = glCheckFramebufferStatus(#GL_FRAMEBUFFER)) <> #GL_FRAMEBUFFER_COMPLETE
			;LOG_ERROR("glCheckFramebufferStatus error 0x%X\n", fboStatus)
			Debug "line error: " + Str(#PB_Compiler_Line) + " (fbo status = " + Str(fboStatus) + ")"
			ProcedureReturn #False
		EndIf
		
		; возвращаем FBO по-умолчанию
		glBindFramebuffer(#GL_FRAMEBUFFER, 0)
	EndProcedure
	Procedure Init()
		UsePNGImageDecoder()
		If #PB_Compiler_Debugger
			OpenWindow(0, 100, 200, 512, 512, "PureBasic Window", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget|#PB_Window_ScreenCentered)
		Else
			OpenWindow(0, 0, 0, 1280, 1024, "test pb", #PB_Window_BorderLess)
		EndIf
		hDC = CreateOpenGLContext(WindowID(0))		
		ShaderID = LoadShadersFromFiles("Data/ex07_shader.vs", "Data/ex07_shader.fs")
		ShaderShadowID = LoadShadersFromFiles("Data/ex07_shaderS.vs", "Data/ex07_shaderS.fs")
		InitTextures()
		CreateFBO()
		Mesh = CreateTorusMesh(1/3, 2/3, 8*4, 16*4)
	EndProcedure
	Procedure RenderShadow()
		Protected.mat4 mViewModel, mModel
		Protected.l program
		
		; установим активный FBO
		glBindFramebuffer(#GL_FRAMEBUFFER, DepthFBO)
		
		; размер вьюпорта должен совпадать с размером текстуры для хранения буфера глубины
		glViewport_(0, 0, #DEPTH_TEXTURE_WIDTH, #DEPTH_TEXTURE_HEIGHT)
		
		; отключаем вывод цвета
		glColorMask_(#GL_FALSE, #GL_FALSE, #GL_FALSE, #GL_FALSE)
		
		; включаем вывод буфера глубины
		glDepthMask_(#GL_TRUE)
		
		; очищаем буфер глубины перед его заполнением
		glClear_(#GL_DEPTH_BUFFER_BIT)
		
		; отключаем отображение внешних граней объекта, оставляя внутренние
		glCullFace_(#GL_FRONT)
		
		; выполним рендер сцены с использованием шейдерной программы и камеры источника освещения
		program = ShaderShadowID
		glUseProgram(program)
		Mat4_MulM(mModel, Light_OffsetMatrix, Model_Matrix)
		Mat4_MulM(mViewModel, Light_ViewMatrix, mModel)
		glUniformMatrix4fv(glGetUniformLocation(program, @"u_mProj"), 1, #True, @Light_ProjectionMatrix)
		glUniformMatrix4fv(glGetUniformLocation(program, @"u_mViewModel"), 1, #True, @mViewModel)
		glEnable_(#GL_DEPTH_TEST)
		DrawMesh(Mesh)
	EndProcedure
	Procedure BuildLightMatrix(*mLightProjView.mat4)
		; матрица сдвига текстурных координат
		Protected.mat4 bias, mProjView, mView
		Mat4_SetL1(bias, 0.5, 0.0, 0.0, 0.5)
		Mat4_SetL2(bias, 0.0, 0.5, 0.0, 0.5)
		Mat4_SetL3(bias, 0.0, 0.0, 0.5, 0.5)
		Mat4_SetL4(bias, 0.0, 0.0, 0.0, 1.0)
		
		; расчитаем необходимые матрицы
		Mat4_MulM(mProjView, Light_ProjectionMatrix, Light_ViewMatrix)
		Mat4_MulM(*mLightProjView, bias, mProjView)
	EndProcedure
	Procedure RenderScene()
		Protected.mat4 mViewModel, mLight
		Protected.vec3 dir
		glViewport_(0, 0, WindowWidth(0), WindowHeight(0))
		glColorMask_(#GL_TRUE, #GL_TRUE, #GL_TRUE, #GL_TRUE)
		glDepthMask_(#GL_TRUE)
		glBindFramebuffer(#GL_FRAMEBUFFER, 0)
		
		glUseProgram(ShaderID)
		BuildLightMatrix(mLight)
		Mat4_MulM(mViewModel, Camera_ViewMatrix, Model_Matrix)
		Quat_DivV_Setup(Camera_Rotation)
		Quat_DivV(dir, Light_Direction)
		glUniformMatrix4fv(glGetUniformLocation(shaderID, @"u_mProj"), 1, #True, @Camera_ProjectionMatrix)
		glUniformMatrix4fv(glGetUniformLocation(shaderID, @"u_mView"), 1, #True, @Camera_ViewMatrix)
		glUniformMatrix4fv(glGetUniformLocation(shaderID, @"u_mModel"), 1, #True, @Model_Matrix)
		glUniformMatrix4fv(glGetUniformLocation(shaderID, @"u_mLight"), 1, #True, @mLight)
		glUniform3fv(glGetUniformLocation(shaderID, @"u_LigthDir"), 1, @dir)
		glEnable_(#GL_DEPTH_TEST)
		DrawMesh(Mesh)
	EndProcedure
	Procedure RunStart()
		Protected.l Event, Quit
		Repeat
			Event = WaitWindowEvent(1)
			If GetAsyncKeyState_(#VK_ESCAPE)
				quit = 1
			EndIf
			Select Event
				Case #PB_Event_CloseWindow	
					Quit = 1
			EndSelect
			glClearColor_(0.3,0.3,0.3,1)
			glClear_(#GL_COLOR_BUFFER_BIT | #GL_DEPTH_BUFFER_BIT)
			Model_Update()
			Camera_Update()
			Light_Update()
			RenderShadow()
			RenderScene()
			SwapBuffers_(hDC)
		Until Quit = 1
	EndProcedure
;}



Init()
RunStart()


























; IDE Options = PureBasic 5.11 (Windows - x86)
; CursorPosition = 251
; FirstLine = 75
; Folding = A3H9-
; EnableXP
; DisableDebugger
; EnableCompileCount = 1054
; EnableBuildCount = 0
; EnableExeConstant